/*
 * Monkey-Moore - A simple and powerful relative search tool
 * Copyright (C) 2007 Ricardo J. Ricken (Darkl0rd)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef MONKEY_THREAD_HPP
#define MONKEY_THREAD_HPP

#include <wx/wxprec.h>

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif

#include "constants.hpp"
#include "monkey_moore.hpp"

#include <wx/file.h>
#include <vector>

using namespace std;

struct SearchParameters
{
   SearchParameters (wxFile *file, const wxString &keyw, const wxString &pattern, const wxChar wcard) :
   file_ptr(file), keyword(keyw), pattern(pattern), wildcard(wcard), search_type(relative) {}

   SearchParameters (wxFile *file, vector <short> vals) :
   file_ptr(file), values(vals), search_type(value_scan) {}

   long keylen () const {
      return search_type == static_cast <long> (relative ? keyword.length() : values.size());
   }

   enum { relative, value_scan } search_type;
   wxFile *file_ptr;

   wxString keyword;
   wxString pattern;
   wxChar wildcard;

   vector <short> values;
};

class SearchThread : public wxThread
{
public:
   SearchThread (SearchParameters p, vector <result_type> &results, wxMutex &mutex, MonkeyPrefs &mp, MonkeyFrame *mf) :
   wxThread(), m_info(p), m_results(results), m_mutex(mutex), m_prefs(mp), m_frame(mf) {}

   /**
   * Method called when the thread is executed.
   * @return Thread's return code.
   */
   virtual void *Entry ()
   {
      MonkeyMoore *moore = m_info.search_type == SearchParameters::relative ?
         new MonkeyMoore(m_info.keyword, m_info.wildcard, m_info.pattern) :
         new MonkeyMoore(m_info.values);
      
      const wxFileOffset size = m_info.file_ptr->Length();
      const long kw_size = m_info.keylen();
      const int block_size = m_prefs.getInt(wxT("search-buffer-size"));
      const int safeblock_size = block_size + kw_size;

      const long num_blocks = static_cast <long> (size / block_size);
      const int remainder = size % block_size;
      wxFileOffset offset_base = 0;

      // locks the mutex so we can access data in the main thread
      wxMutexLocker lock(m_mutex);

      // splits the file in fixed size blocks and search one by one,
      // so we can have a better control over memory usage.
      for (int i = 0; i < num_blocks; i++)
      {
         m_frame->SetCurrentProgress(calcPercentage(i, num_blocks, remainder > 0));
         m_info.file_ptr->Seek(offset_base, wxFromStart);

         // checks if the search was aborted in the main thread
         if (m_frame->IsSearchAborted())
         {
            CleanUpAndNotify(MonkeyThread_JobAborted);
            return NULL;
         }

         // starting with the second block, we need to search also in the keyword-lenght bytes
         // of the previous block, to prevent intersecctions
         int curblock_size = i == 0 ? block_size : safeblock_size;

         u8 *block_data = new u8[curblock_size];
         m_info.file_ptr->Read(block_data, curblock_size);

         SearchAndAppendResults(*moore, block_data, curblock_size, offset_base);

         offset_base += i == 0 ? block_size - kw_size : block_size;
         delete [] block_data;
      }

      // searches the remainder (not integral) block
      if (remainder)
      {
         m_info.file_ptr->Seek(offset_base, wxFromStart);

         int curblock_size = size < block_size ? remainder : remainder + kw_size;
         wxASSERT((offset_base + curblock_size) == size);

         u8 *remainder_data = new u8[curblock_size];
         m_info.file_ptr->Read(remainder_data, curblock_size);

         SearchAndAppendResults(*moore, remainder_data, curblock_size, offset_base);

         delete [] remainder_data;
      }

      // generates previews
      for (vector <result_type>::iterator i = m_results.begin(); i != m_results.end(); i++)
         i->second = GeneratePreview(i->first.first, i->first.second);

      m_frame->SetCurrentProgress(100);
      CleanUpAndNotify(MonkeyThread_JobFinished);

      return NULL;
   }

private:
   /**
   * Searches and corrects the offset of the results, based on the currentblock offset.
   * @param data pointer to current block data
   * @param len block length
   * @param offset_base current block real offset
   */
   void SearchAndAppendResults (MonkeyMoore &moore, const u8 *data, const long len, const wxFileOffset offset_base)
   {
      vector <relative_type> cur_results = moore.search(data, len);

      for (vector <relative_type>::iterator j = cur_results.begin(); j != cur_results.end(); j++)
         m_results.push_back(make_pair(make_pair(offset_base + j->first, j->second), wxT("")));
   }

   /**
   * Generates a preview for each search result.
   * @param offset result offset in the file
   * @param table equivalency table
   * @return Result preview.
   */
   wxString GeneratePreview (const wxFileOffset offset, const equivalency_type &table)
   {
      const int width = m_prefs.getInt(wxT("preview-width"));

      // changes the offset so we can put the keyword in the center of the preview
      const wxFileOffset nice_pos = offset - (width / 2) + static_cast <int> (m_info.keyword.size() / 2);
      const wxFileOffset read_offset = nice_pos >= 0 ? nice_pos : 0;

      m_info.file_ptr->Seek(read_offset, wxFromStart);

      u8 *raw_data = new u8[width];
      m_info.file_ptr->Read(raw_data, width);

      wxString result;

      if (m_info.search_type == SearchParameters::relative)
      {
         // maps the table entries
         map <u8, wxChar> cur_table;

         for (int i = 0; i <= 0xff; i++)
            cur_table[static_cast <u8> (i)] = wxT('#');

         // generates the table
         for (equivalency_type::const_iterator i = table.begin(); i != table.end(); i++)
         {
            if (!m_info.pattern.length() && (i->first == wxT('A') || i->first == wxT('a')))
               for (int j = 0; j < 26; j++)
                  cur_table[i->second + static_cast <u8> (j)] = i->first + static_cast <wxChar> (j);
            else
               cur_table[i->second] = i->first;
         }

         // replace the available characters
         for (u8 *start = raw_data; start != raw_data + width; start++)
            result += cur_table[*start];
      }
      else
      {
         for (u8 *start = raw_data; start != raw_data + width; start++)
            result += wxString::Format(wxT("%02X "), *start);

         // erase trailing whitespace
         result.erase(result.length() - 1);
      }

      delete [] raw_data;
      return result;
   }

   /**
   * Method called to free the allocated resources and notify the main thread
   * that we are done (either finished searching or aborted).
   * @param id notification id
   */
   inline void CleanUpAndNotify (const long id)
   {
      delete m_info.file_ptr;

      wxCommandEvent event(wxEVT_COMMAND_MENU_SELECTED, id);
      m_frame->AddPendingEvent(event);
   }

   /**
   * Calculates the current percentage based on the actual block and the total number of blocks.
   * @param block current block
   * @param num_blocks number of blocks
   * @param remainder indicates the presence of a remainder block
   * @return Percentage value in the 0-100 range.
   */
   inline int calcPercentage (const int block, const long num_blocks, const bool remainder) const {
      return static_cast <int> ((block * 100) / (num_blocks + (remainder ? 1 : 0)));
   }

   SearchParameters m_info;
   MonkeyFrame *m_frame;
   MonkeyPrefs &m_prefs;
   wxMutex &m_mutex;
   vector <result_type> &m_results;
};

#endif //~MONKEY_THREAD_HPP
